﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using Infrastructure.Interfaces;
using System.Linq.Expressions;
using Infrastructure.Tools.Events;

namespace MVVMBasicRIALibrary
{
    public class BasicRepository<T> : IRepository<T> where T : Entity
    {
        private readonly List<Tuple<EntityQuery<T>, LoadOperation, Action<IEnumerable<T>>>> callbackCollectionList = new List<Tuple<EntityQuery<T>,LoadOperation,Action<IEnumerable<T>>>>();
        private readonly List<Tuple<EntityQuery<T>, LoadOperation, Action<T>>> callbackList = new List<Tuple<EntityQuery<T>,LoadOperation,Action<T>>>();
        private DomainContext _context;
        private EntityQuery<T> _entityQuery;
        private EntitySet<T> _entitySet;
        public event EventHandler<ResultOperationEventArgs> NotifyResultOperation;

        private DomainContext Context
        {
            get { return _context ?? (_context = GetCurrentUnitOfWork<BasicUnitOfWork>().Context); }
        }

        public TUnitOfWork GetCurrentUnitOfWork<TUnitOfWork>() where TUnitOfWork : IUnitOfWork
        {
            return (TUnitOfWork)UnitOfWork.Current;
        }

        private EntityQuery<T> EFEntityQuery
        {
            get
            {
                var methodQuery = Context.GetType().GetMethods().Where(x => x.ReturnType == typeof(EntityQuery<T>)).FirstOrDefault();
                if (methodQuery != null)
                {
                    var result = methodQuery.Invoke(_context, new object[] { });
                    _entityQuery = result as EntityQuery<T>;
                }
                else
                {
                    _entityQuery = null;
                }
                return _entityQuery;
            }
        }

        private EntitySet<T> EFEntitySet
        {
            get { return _entitySet ?? (_entitySet = this._context.EntityContainer.GetEntitySet<T>()); }
        }

        public EntityQuery<T> GetQuery()
        {
            return EFEntityQuery;
        }

        public void GetAll(Action<IEnumerable<T>> callback)
        {
            var query =  GetQuery();
            RunQuery(query, callback);
        }

        public void GetByFilter(Expression<Func<T, bool>> filter, Action<IEnumerable<T>> callback)
        {
            var query = GetQuery().Where(filter);
            RunQuery(query, callback);
        }

        public void GetFirst(Expression<Func<T, bool>> filter, Action<T> callback) 
        {
            var query = GetQuery().Where(filter);
            RunQuery2(query, callback);
        }

        public void GetById(int id, Action<T> callback)
        {
            var query = GetQuery().Where(x => x.GetIdentity().ToString() == id.ToString());
            RunQuery2(query, callback);
        }

        private void RunQuery2(EntityQuery<T> query, Action<T> callback)
        {
            var loadOperation = _context.Load(query);
            var tuple = new Tuple<EntityQuery<T>, LoadOperation, Action<T>>(query, loadOperation, callback);
            callbackList.Add(tuple);
            loadOperation.Completed += OnLoadEntityCompleted;
        }

        private void RunQuery(EntityQuery<T> query, Action<IEnumerable<T>> callback)
        {
            var loadOperation = _context.Load(query);
            var tuple = new Tuple<EntityQuery<T>, LoadOperation, Action<IEnumerable<T>>>(query, loadOperation, callback);
            callbackCollectionList.Add(tuple);
            loadOperation.Completed += OnLoadEntitiesCompleted;
        }
      
        private void OnLoadEntitiesCompleted(object sender, EventArgs e)
        {
            var loadOperation = sender as LoadOperation;
            if (loadOperation != null)
            {
                loadOperation.Completed -= OnLoadEntitiesCompleted;
                var entities = loadOperation.Entities.Cast<T>().AsEnumerable();
                var tuple = callbackCollectionList.Where(x => x.Item2 == loadOperation).FirstOrDefault();
                if (tuple != null)
                {
                    tuple.Item3(entities);
                    callbackCollectionList.Remove(tuple);
                }
            }
            
        }
        
        private void OnLoadEntityCompleted(object sender, EventArgs e)
        {
            var loadOperation = sender as LoadOperation;
            if (loadOperation != null)
            {
                loadOperation.Completed -= OnLoadEntitiesCompleted;
                var entity = loadOperation.Entities.Cast<T>().AsEnumerable().FirstOrDefault();
                var tuple = callbackList.Where(x => x.Item2 == loadOperation).FirstOrDefault();
                if (tuple != null)
                {
                    tuple.Item3(entity);
                    callbackList.Remove(tuple);
                }
            }
            
        }
        
        public void Delete(T entity)
        {
            this.EFEntitySet.Remove(entity);
        }

        public void Add(T entity)
        {
            this.EFEntitySet.Add(entity);
        }

        public void Attach(T entity)
        {
            this.EFEntitySet.Attach(entity);
        }

        public void SaveChanges()
        {
            this.Context.SubmitChanges(OnSubmitCompleted, null);
        }

        private void OnSubmitCompleted(SubmitOperation so)
        {
            if (so.HasError)
            {
                so.MarkErrorAsHandled();
            }

            if (NotifyResultOperation != null)
            {
                this._context.RejectChanges();
                var result = new ResultOperationEventArgs
                {
                    HasError = so.HasError,
                    IsCanceled = so.IsCanceled,
                    IsComplete = so.IsComplete,
                    IsErrorHandled = so.IsErrorHandled
                };

                var domainex = so.Error as DomainOperationException;
                if(domainex != null)
                {
                    result.Error = new EntityDomainOperationException(domainex.Message, (EntityOperationErrorStatus) domainex.Status, domainex.ErrorCode);
                }
                else
                {
                    result.Error = so.Error;
                }
                
                NotifyResultOperation(this, result);
            }
        }
    }
}

